/* Copyright (c) 2016 the Civetweb developers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

static int
url_encoded_field_found(const struct mg_connection *conn,
  const char *key,
  size_t key_len,
  const char *filename,
  size_t filename_len,
  char *path,
  size_t path_len,
struct mg_form_data_handler *fdh)
{
  char key_dec[1024];
  char filename_dec[1024];
  int key_dec_len;
  int filename_dec_len;
  int ret;

  key_dec_len =
    mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);

  if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) {
    return FORM_FIELD_STORAGE_SKIP;
  }

  if (filename) {
    filename_dec_len = mg_url_decode(filename,
      (int)filename_len,
      filename_dec,
      (int)sizeof(filename_dec),
      1);

    if (((size_t)filename_dec_len >= (size_t)sizeof(filename_dec))
      || (filename_dec_len < 0)) {
      /* Log error message and skip this field. */
      mg_cry(conn, "%s: Cannot decode filename", __func__);
      return FORM_FIELD_STORAGE_SKIP;
    }
  }
  else {
    filename_dec[0] = 0;
  }

  ret =
    fdh->field_found(key_dec, filename_dec, path, path_len, fdh->user_data);

  if ((ret & 0xF) == FORM_FIELD_STORAGE_GET) {
    if (fdh->field_get == NULL) {
      mg_cry(conn, "%s: Function \"Get\" not available", __func__);
      return FORM_FIELD_STORAGE_SKIP;
    }
  }
  if ((ret & 0xF) == FORM_FIELD_STORAGE_STORE) {
    if (fdh->field_store == NULL) {
      mg_cry(conn, "%s: Function \"Store\" not available", __func__);
      return FORM_FIELD_STORAGE_SKIP;
    }
  }

  return ret;
}

static int
url_encoded_field_get(const struct mg_connection *conn,
  const char *key,
  size_t key_len,
  const char *value,
  size_t value_len,
struct mg_form_data_handler *fdh)
{
  char key_dec[1024];

  char *value_dec = (char*)mg_malloc(value_len + 1);
  int value_dec_len;

  if (!value_dec) {
    /* Log error message and stop parsing the form data. */
    mg_cry(conn,
      "%s: Not enough memory (required: %lu)",
      __func__,
      (unsigned long)(value_len + 1));
    return FORM_FIELD_STORAGE_ABORT;
  }

  mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);

  value_dec_len =
    mg_url_decode(value, (int)value_len, value_dec, (int)value_len + 1, 1);

  return fdh->field_get(key_dec,
    value_dec,
    (size_t)value_dec_len,
    fdh->user_data);
}

static int
field_stored(const struct mg_connection *conn,
  const char *path,
  size_t file_size,
struct mg_form_data_handler *fdh)
{
  /* Equivalent to "upload" callback of "mg_upload". */

  (void)conn; /* we do not need mg_cry here, so conn is currently unused */

  return fdh->field_store(path, file_size, fdh->user_data);
}

static const char *
search_boundary(const char *buf,
  size_t buf_len,
  const char *boundary,
  size_t boundary_len)
{
  /* We must do a binary search here, not a string search, since the buffer
  * may contain '\x00' bytes, if binary data is transfered. */
  int clen = (int)buf_len - (int)boundary_len - 4;
  int i;

  for (i = 0; i <= clen; i++) {
    if (!memcmp(buf + i, "\r\n--", 4)) {
      if (!memcmp(buf + i + 4, boundary, boundary_len)) {
        return buf + i;
      }
    }
  }
  return NULL;
}

int
mg_handle_form_request(struct mg_connection *conn,
struct mg_form_data_handler *fdh)
{
  const char *content_type;
  char path[512];
  char buf[1024];
  int field_storage;
  int buf_fill = 0;
  int r;
  int field_count = 0;
  struct file fstore = STRUCT_FILE_INITIALIZER;
  size_t file_size = 0; /* init here, to a avoid a false positive
                        "uninitialized variable used" warning */

  int has_body_data =
    (conn->request_info.content_length > 0) || (conn->is_chunked);

  /* There are three ways to encode data from a HTML form:
  * 1) method: GET (default)
  *    The form data is in the HTTP query string.
  * 2) method: POST, enctype: "application/x-www-form-urlencoded"
  *    The form data is in the request body.
  *    The body is url encoded (the default encoding for POST).
  * 3) method: POST, enctype: "multipart/form-data".
  *    The form data is in the request body of a multipart message.
  *    This is the typical way to handle file upload from a form.
  */

  if (!has_body_data) {
    const char *data;

    if (strcmp(conn->request_info.request_method, "GET")) {
      /* No body data, but not a GET request.
      * This is not a valid form request. */
      return -1;
    }

    /* GET request: form data is in the query string. */
    /* The entire data has already been loaded, so there is no nead to
    * call mg_read. We just need to split the query string into key-value
    * pairs. */
    data = conn->request_info.query_string;
    if (!data) {
      /* No query string. */
      return -1;
    }

    /* Split data in a=1&b=xy&c=3&c=4 ... */
    while (*data) {
      const char *val = strchr(data, '=');
      const char *next;
      ptrdiff_t keylen, vallen;

      if (!val) {
        break;
      }
      keylen = val - data;

      /* In every "field_found" callback we ask what to do with the
      * data ("field_storage"). This could be:
      * FORM_FIELD_STORAGE_SKIP (0) ... ignore the value of this field
      * FORM_FIELD_STORAGE_GET (1) ... read the data and call the get
      *                              callback function
      * FORM_FIELD_STORAGE_STORE (2) ... store the data in a file
      * FORM_FIELD_STORAGE_READ (3) ... let the user read the data
      *                               (for parsing long data on the fly)
      *                               (currently not implemented)
      * FORM_FIELD_STORAGE_ABORT (flag) ... stop parsing
      */
      memset(path, 0, sizeof(path));
      field_count++;
      field_storage = url_encoded_field_found(conn,
        data,
        (size_t)keylen,
        NULL,
        0,
        path,
        sizeof(path) - 1,
        fdh);

      val++;
      next = strchr(val, '&');
      if (next) {
        vallen = next - val;
        next++;
      }
      else {
        vallen = (ptrdiff_t)strlen(val);
        next = val + vallen;
      }

      if (field_storage == FORM_FIELD_STORAGE_GET) {
        /* Call callback */
        url_encoded_field_get(
          conn, data, (size_t)keylen, val, (size_t)vallen, fdh);
      }
      if (field_storage == FORM_FIELD_STORAGE_STORE) {
        /* Store the content to a file */
        if (mg_fopen(conn, path, "wb", &fstore) == 0) {
          fstore.fp = NULL;
        }
        file_size = 0;
        if (fstore.fp != NULL) {
          size_t n =
            (size_t)fwrite(val, 1, (size_t)vallen, fstore.fp);
          if ((n != (size_t)vallen) || (ferror(fstore.fp))) {
            mg_cry(conn,
              "%s: Cannot write file %s",
              __func__,
              path);
            fclose(fstore.fp);
            fstore.fp = NULL;
            remove_bad_file(conn, path);
          }
          file_size += (size_t)n;

          if (fstore.fp) {
            r = fclose(fstore.fp);
            if (r == 0) {
              /* stored successfully */
              field_stored(conn, path, file_size, fdh);
            }
            else {
              mg_cry(conn,
                "%s: Error saving file %s",
                __func__,
                path);
              remove_bad_file(conn, path);
            }
            fstore.fp = NULL;
          }

        }
        else {
          mg_cry(conn, "%s: Cannot create file %s", __func__, path);
        }
      }

      /* if (field_storage == FORM_FIELD_STORAGE_READ) { */
      /* The idea of "field_storage=read" is to let the API user read
      * data chunk by chunk and to some data processing on the fly.
      * This should avoid the need to store data in the server:
      * It should neither be stored in memory, like
      * "field_storage=get" does, nor in a file like
      * "field_storage=store".
      * However, for a "GET" request this does not make any much
      * sense, since the data is already stored in memory, as it is
      * part of the query string.
      */
      /* } */

      if ((field_storage & FORM_FIELD_STORAGE_ABORT)
        == FORM_FIELD_STORAGE_ABORT) {
        /* Stop parsing the request */
        break;
      }

      /* Proceed to next entry */
      data = next;
    }

    return field_count;
  }

  content_type = mg_get_header(conn, "Content-Type");

  if (!content_type
    || !mg_strcasecmp(content_type, "APPLICATION/X-WWW-FORM-URLENCODED")) {
    /* The form data is in the request body data, encoded in key/value
    * pairs. */
    int all_data_read = 0;

    /* Read body data and split it in a=1&b&c=3&c=4 ... */
    /* The encoding is like in the "GET" case above, but here we read data
    * on the fly */
    for (;;) {
      /* TODO(high): Handle (text) fields with data size > sizeof(buf). */
      const char *val;
      const char *next;
      ptrdiff_t keylen, vallen;
      ptrdiff_t used;
      int end_of_key_value_pair_found = 0;

      if ((size_t)buf_fill < (sizeof(buf) - 1)) {

        size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
        r = mg_read(conn, buf + (size_t)buf_fill, to_read);
        if (r < 0) {
          /* read error */
          return -1;
        }
        if (r != (int)to_read) {
          /* TODO: Create a function to get "all_data_read" from
          * the conn object. Add data is read if the Content-Length
          * has been reached, or if chunked encoding is used and
          * the end marker has been read, or if the connection has
          * been closed. */
          all_data_read = 1;
        }
        buf_fill += r;
        buf[buf_fill] = 0;
        if (buf_fill < 1) {
          break;
        }
      }

      val = strchr(buf, '=');

      if (!val) {
        break;
      }
      keylen = val - buf;
      val++;

      /* Call callback */
      memset(path, 0, sizeof(path));
      field_count++;
      field_storage = url_encoded_field_found(conn,
        buf,
        (size_t)keylen,
        NULL,
        0,
        path,
        sizeof(path) - 1,
        fdh);

      if ((field_storage & FORM_FIELD_STORAGE_ABORT)
        == FORM_FIELD_STORAGE_ABORT) {
        /* Stop parsing the request */
        break;
      }

      if (field_storage == FORM_FIELD_STORAGE_STORE) {
        if (mg_fopen(conn, path, "wb", &fstore) == 0) {
          fstore.fp = NULL;
        }
        file_size = 0;
        if (!fstore.fp) {
          mg_cry(conn, "%s: Cannot create file %s", __func__, path);
        }
      }

      /* Loop to read values larger than sizeof(buf)-keylen-2 */
      do {
        next = strchr(val, '&');
        if (next) {
          vallen = next - val;
          next++;
          end_of_key_value_pair_found = 1;
        }
        else {
          vallen = (ptrdiff_t)strlen(val);
          next = val + vallen;
        }

        if (fstore.fp) {
          size_t n =
            (size_t)fwrite(val, 1, (size_t)vallen, fstore.fp);
          if ((n != (size_t)vallen) || (ferror(fstore.fp))) {
            mg_cry(conn,
              "%s: Cannot write file %s",
              __func__,
              path);
            fclose(fstore.fp);
            fstore.fp = NULL;
            remove_bad_file(conn, path);
          }
          file_size += (size_t)n;
        }
        if (field_storage == FORM_FIELD_STORAGE_GET) {
          if (!end_of_key_value_pair_found && !all_data_read) {
            /* TODO: check for an easy way to get longer data */
            mg_cry(conn,
              "%s: Data too long for callback",
              __func__);
            return -1;
          }
          /* Call callback */
          url_encoded_field_get(
            conn, buf, (size_t)keylen, val, (size_t)vallen, fdh);
        }

        if (!end_of_key_value_pair_found) {
          /* TODO: read more data */
          break;
        }

      } while (!end_of_key_value_pair_found);

      if (fstore.fp) {
        r = fclose(fstore.fp);
        if (r == 0) {
          /* stored successfully */
          field_stored(conn, path, file_size, fdh);
        }
        else {
          mg_cry(conn, "%s: Error saving file %s", __func__, path);
          remove_bad_file(conn, path);
        }
        fstore.fp = NULL;
      }

      /* Proceed to next entry */
      used = next - buf;
      memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
      buf_fill -= (int)used;
    }

    return field_count;
  }

  if (!mg_strncasecmp(content_type, "MULTIPART/FORM-DATA;", 20)) {
    /* The form data is in the request body data, encoded as multipart
    * content (see https://www.ietf.org/rfc/rfc1867.txt,
    * https://www.ietf.org/rfc/rfc2388.txt). */
    const char *boundary;
    size_t bl;
    ptrdiff_t used;
    struct mg_request_info part_header;
    char *hbuf, *hend, *fbeg, *fend, *nbeg, *nend;
    const char *content_disp;
    const char *next;

    memset(&part_header, 0, sizeof(part_header));

    /* There has to be a BOUNDARY definition in the Content-Type header */
    if (mg_strncasecmp(content_type + 21, "BOUNDARY=", 9)) {
      /* Malformed request */
      return -1;
    }

    boundary = content_type + 30;
    bl = strlen(boundary);

    if (bl + 800 > sizeof(buf)) {
      /* Sanity check:  The algorithm can not work if bl >= sizeof(buf),
      * and it will not work effectively, if the buf is only a few byte
      * larger than bl, or it buf can not hold the multipart header
      * plus the boundary.
      * Check some reasonable number here, that should be fulfilled by
      * any reasonable request from every browser. If it is not
      * fulfilled, it might be a hand-made request, intended to
      * interfere with the algorithm. */
      return -1;
    }

    for (;;) {

      r = mg_read(conn,
        buf + (size_t)buf_fill,
        sizeof(buf) - 1 - (size_t)buf_fill);
      if (r < 0) {
        /* read error */
        return -1;
      }
      buf_fill += r;
      buf[buf_fill] = 0;
      if (buf_fill < 1) {
        /* No data */
        return -1;
      }

      if (buf[0] != '-' || buf[1] != '-') {
        /* Malformed request */
        return -1;
      }
      if (strncmp(buf + 2, boundary, bl)) {
        /* Malformed request */
        return -1;
      }
      if (buf[bl + 2] != '\r' || buf[bl + 3] != '\n') {
        /* Every part must end with \r\n, if there is another part.
        * The end of the request has an extra -- */
        if (((size_t)buf_fill != (size_t)(bl + 6))
          || (strncmp(buf + bl + 2, "--\r\n", 4))) {
          /* Malformed request */
          return -1;
        }
        /* End of the request */
        break;
      }

      /* Next, we need to get the part header: Read until \r\n\r\n */
      hbuf = buf + bl + 4;
      hend = strstr(hbuf, "\r\n\r\n");
      if (!hend) {
        /* Malformed request */
        return -1;
      }

      parse_http_headers(&hbuf, &part_header);
      if ((hend + 2) != hbuf) {
        /* Malformed request */
        return -1;
      }

      /* Skip \r\n\r\n */
      hend += 4;

      /* According to the RFC, every part has to have a header field like:
      * Content-Disposition: form-data; name="..." */
      content_disp = get_header(&part_header, "Content-Disposition");
      if (!content_disp) {
        /* Malformed request */
        return -1;
      }

      /* Get the mandatory name="..." part of the Content-Disposition
      * header. */
      nbeg = (char*)strstr(content_disp, "name=\"");
      if (!nbeg) {
        /* Malformed request */
        return -1;
      }
      nbeg += 6;
      nend = strchr(nbeg, '\"');
      if (!nend) {
        /* Malformed request */
        return -1;
      }

      /* Get the optional filename="..." part of the Content-Disposition
      * header. */
      fbeg = (char*)strstr(content_disp, "filename=\"");
      if (fbeg) {
        fbeg += 10;
        fend = strchr(fbeg, '\"');
        if (!fend) {
          /* Malformed request (the filename field is optional, but if
          * it exists, it needs to be terminated correctly). */
          return -1;
        }

        /* TODO: check Content-Type */
        /* Content-Type: application/octet-stream */

      }
      else {
        fend = fbeg;
      }

      memset(path, 0, sizeof(path));
      field_count++;
      field_storage = url_encoded_field_found(conn,
        nbeg,
        (size_t)(nend - nbeg),
        fbeg,
        (size_t)(fend - fbeg),
        path,
        sizeof(path) - 1,
        fdh);

      /* If the boundary is already in the buffer, get the address,
      * otherwise next will be NULL. */
      next = search_boundary(hbuf,
        (size_t)((buf - hbuf) + buf_fill),
        boundary,
        bl);

      if (field_storage == FORM_FIELD_STORAGE_GET) {
        if (!next) {
          /* TODO: check for an easy way to get longer data */
          mg_cry(conn, "%s: Data too long for callback", __func__);
          return -1;
        }

        /* Call callback */
        url_encoded_field_get(conn,
          nbeg,
          (size_t)(nend - nbeg),
          hend,
          (size_t)(next - hend),
          fdh);
      }

      if (field_storage == FORM_FIELD_STORAGE_STORE) {
        /* Store the content to a file */
        size_t towrite, n;
        if (mg_fopen(conn, path, "wb", &fstore) == 0) {
          fstore.fp = NULL;
        }
        file_size = 0;

        if (!fstore.fp) {
          mg_cry(conn, "%s: Cannot create file %s", __func__, path);
        }

        while (!next) {
          /* Set "towrite" to the number of bytes available
          * in the buffer */
          towrite = (size_t)(buf - hend + buf_fill);
          /* Subtract the boundary length, to deal with
          * cases the boundary is only partially stored
          * in the buffer. */
          towrite -= bl + 4;

          if (fstore.fp) {

            /* Store the content of the buffer. */
            n = (size_t)fwrite(hend, 1, towrite, fstore.fp);
            if ((n != towrite) || (ferror(fstore.fp))) {
              mg_cry(conn,
                "%s: Cannot write file %s",
                __func__,
                path);
              fclose(fstore.fp);
              fstore.fp = NULL;
              remove_bad_file(conn, path);
            }
            file_size += (size_t)n;
          }

          memmove(buf, hend + towrite, bl + 4);
          buf_fill = (int)(bl + 4);
          hend = buf;

          /* Read new data */
          r = mg_read(conn,
            buf + (size_t)buf_fill,
            sizeof(buf) - 1 - (size_t)buf_fill);
          if (r < 0) {
            /* read error */
            return -1;
          }
          buf_fill += r;
          buf[buf_fill] = 0;
          if (buf_fill < 1) {
            /* No data */
            return -1;
          }

          /* Find boundary */
          next = search_boundary(buf, (size_t)buf_fill, boundary, bl);
        }

        if (fstore.fp) {
          towrite = (size_t)(next - hend);
          n = (size_t)fwrite(hend, 1, towrite, fstore.fp);
          if ((n != towrite) || (ferror(fstore.fp))) {
            mg_cry(conn,
              "%s: Cannot write file %s",
              __func__,
              path);
            fclose(fstore.fp);
            fstore.fp = NULL;
            remove_bad_file(conn, path);
          }
          file_size += (size_t)n;
        }

        if (fstore.fp) {
          r = fclose(fstore.fp);
          if (r == 0) {
            /* stored successfully */
            field_stored(conn, path, file_size, fdh);
          }
          else {
            mg_cry(conn,
              "%s: Error saving file %s",
              __func__,
              path);
            remove_bad_file(conn, path);
          }
          fstore.fp = NULL;
        }
      }

      if ((field_storage & FORM_FIELD_STORAGE_ABORT)
        == FORM_FIELD_STORAGE_ABORT) {
        /* Stop parsing the request */
        return -1;
      }

      /* Remove from the buffer */
      used = next - buf + 2;
      memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
      buf_fill -= (int)used;
    }

    /* All parts handled */
    return field_count;
  }

  /* Unknown Content-Type */
  return -1;
}
